﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;

namespace Ensynch.FIM
{
    public class FimQueryAttributeDictionary
    {
        private FimQuery query;
        private Dictionary<string, FimQueryAttribute> dictionary = new Dictionary<string,FimQueryAttribute>();

        public FimQueryAttributeDictionary(FimQuery query)
        {
            this.query = query;
        }

        /// <summary>
        /// Key is automatically trimmed: [foo] --> foo.
        /// But the raw key is preserved: AttributeRaw = "[foo]".
        /// </summary>
        public FimQueryAttribute this[string key]
        {
            get
            {
                string keyTrimmed = FimQueryAttribute.Trim(key);
                if (!dictionary.ContainsKey(keyTrimmed))
                {
                    dictionary.Add(keyTrimmed, new FimQueryAttribute(query, key));
                    dictionary[keyTrimmed].HasChanged += new EventHandler(FimQueryAttribute_HasChanged);
                    ClearCalculatedAttributeLists();
                }
                return dictionary[keyTrimmed];
            }
        }

        /// <summary>
        /// This collection will always contain the full list of attributes,
        /// even when SelectStar = true. However, note that it is possible for
        /// this collection to be empty, when SelectStar = false and the
        /// dictionary is emtpy.
        /// 
        /// It is the responsibility of the caller to toggle SelectStar when IsEmtpy.
        /// </summary>
        public IEnumerable<string> Keys
        {
            get
            {
                ensureAttributes();
                return dictionary.Keys.AsEnumerable();
            }
        }

        /// <summary>
        /// This collection will always contain the full list of attributes,
        /// even when SelectStar = true. However, note that it is possible for
        /// this collection to be empty, when SelectStar = false and the
        /// dictionary is emtpy.
        /// 
        /// It is the responsibility of the caller to toggle SelectStar when IsEmtpy.
        /// </summary>
        public IEnumerable<FimQueryAttribute> Values
        {
            get
            {
                ensureAttributes();
                return dictionary.Values.AsEnumerable();
            }
        }

        private bool selectStar = false;
        /// <summary>
        /// It is the responsibility of the caller to toggle SelectStar when IsEmtpy.
        /// </summary>
        public bool SelectStar
        {
            get
            {
                return selectStar;
            }
            set
            {
                if (selectStar != value)
                {
                    this.Clear();
                    selectStar = value;
                }
            }
        }

        private string[] enumerateAttributes = null;
        public string[] EnumerateAttributes
        {
            get
            {
                if (this.SelectStar)
                {
                    // Sending null to the FIM service will return all attributes.
                    return null;
                }
                if (enumerateAttributes == null)
                {
                    List<string> list = new List<string>();
                    string attribute;
                    foreach (var value in dictionary.Values)
                    {
                        attribute = value.AttributeTrimmed;
                        if (!list.Contains(attribute))
                        {
                            list.Add(attribute);
                        }
                    }
                    enumerateAttributes = list.ToArray();
                }
                return enumerateAttributes;
            }
        }

        private List<DataColumn> dataColumns;
        public List<DataColumn> DataColumns
        {
            get
            {
                if (dataColumns == null)
                {
                    ensureAttributes();
                    dataColumns = new List<DataColumn>();
                    foreach (var attribute in dictionary.Values)
                    {
                        dataColumns.AddRange(attribute.DataColumns);
                    }
                }
                return dataColumns;
            }
        }

        public bool IsEmpty
        {
            get
            {
                foreach (var attribute in dictionary.Values)
                {
                    if (!attribute.IsEmpty)
                    {
                        return false;
                    }
                }
                return true;
            }
        }

        public void SetParameters(Dictionary<string, object> parameters)
        {
            foreach (FimQueryAttribute attribute in this.Values)
            {
                attribute.SetParameters(parameters);
            }
        }

        public void Set(string key, bool includeRaw, params string[] alias)
        {
            this[key].IncludeRaw = includeRaw;
            if (alias != null && alias.Length > 0)
            {
                if (alias.Length > 1)
                {
                    throw new ArgumentException("Only one alias is allowed.", "alias");
                }
                this[key].AliasRaw = alias[0];
            }
        }

        public bool ContainsKey(string key)
        {
            string keyTrimmed = FimQueryAttribute.Trim(key);
            return dictionary.ContainsKey(keyTrimmed);
        }

        /// <summary>
        /// If there are no items in the dictionary and SelectStar = true, then
        /// fill the dictionary with default values.
        /// </summary>
        private void ensureAttributes()
        {
            if (this.SelectStar && dictionary.Count == 0)
            {
                foreach (var item in query.Tools.GetAttributeDictionary(query.ObjectType))
                {
                    this.Set(item.Key, true, "[" + item.Value + "]");
                }
            }
        }

        /// <summary>
        /// If SelectStar = true, explicitly adds all attributes and toggles SelectStar.
        /// </summary>
        public void FlattenAttributeList()
        {
            if (SelectStar)
            {
                ensureAttributes();
                selectStar = false;
            }
        }

        public void ClearCalculatedAttributeLists()
        {
            enumerateAttributes = null;
            dataColumns = null;
        }

        public void Clear()
        {
            foreach (var attribute in dictionary.Values)
            {
                attribute.HasChanged -= new EventHandler(this.FimQueryAttribute_HasChanged);
            }
            // Clear all attributes, forcing a re-calc.
            dictionary.Clear();
            ClearCalculatedAttributeLists();
        }

        public override string ToString()
        {
            StringBuilder buffer = new StringBuilder();
            if (this.SelectStar)
            {
                buffer.Append("*");
            }
            else
            {
                bool first = true;
                string attributeString;
                foreach (var attribute in dictionary.Values)
                {
                    attributeString = attribute.ToString();
                    if (!string.IsNullOrEmpty(attributeString))
                    {
                        if (first)
                        {
                            first = false;
                        }
                        else
                        {
                            buffer.Append("\r\n,");
                        }
                        buffer.Append(attributeString);
                    }
                }
            }
            return buffer.ToString();
        }

        #region Events

        private void FimQueryAttribute_HasChanged(object sender, EventArgs e)
        {
            ClearCalculatedAttributeLists();
        }

        #endregion

    }
}
